package com.stuffthathappens.concurrency;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;

/**
 * @author Eric Burke
 */
public class DemoSwingWorker extends JFrame {
    private Action startAction = new AbstractAction("Start") {
        public void actionPerformed(ActionEvent e) {
            startClicked();
        }
    };
    private Action cancelAction = new AbstractAction("Cancel") {
        public void actionPerformed(ActionEvent e) {
            cancelClicked();
        }
    };
    private final JButton startButton = new JButton(startAction);
    private final JButton cancelButton = new JButton(cancelAction);
    private final JProgressBar progressBar = new JProgressBar();
    private final DefaultListModel nameListModel = new DefaultListModel();
    private final JList nameList = new JList(nameListModel);

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                new DemoSwingWorker().setVisible(true);
            }
        });
    }

    public DemoSwingWorker() {
        super("SwingWorker Demo");
        layoutGui();

        cancelAction.setEnabled(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    private void layoutGui() {
        Container cp = getContentPane();
        cp.add(progressBar, BorderLayout.NORTH);

        cp.add(new JScrollPane(nameList), BorderLayout.CENTER);

        cp.add(createButtonPanel(), BorderLayout.SOUTH);
        progressBar.setVisible(false);

        pack();
        setLocationRelativeTo(null);
    }

    private Component createButtonPanel() {
        Box box = Box.createHorizontalBox();
        box.add(Box.createHorizontalGlue());
        box.add(Box.createHorizontalStrut(4));
        box.add(startButton);
        box.add(Box.createHorizontalStrut(4));
        box.add(cancelButton);
        box.add(Box.createHorizontalStrut(4));
        return box;
    }

    private FetchDataWorker fetchDataWorker;

    private void startClicked() {
        if (startButton.hasFocus()) {
            startButton.transferFocus();
        }
        nameListModel.clear();
        startAction.setEnabled(false);
        cancelAction.setEnabled(true);
        progressBar.setString("Fetching Data...");
        progressBar.setStringPainted(true);
        progressBar.setValue(0);
        progressBar.setVisible(true);

        // create a new SwingWorker and start it
        fetchDataWorker = new FetchDataWorker();
        fetchDataWorker.addPropertyChangeListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if ("progress".equals(evt.getPropertyName())) {
                    progressBar.setValue((Integer) evt.getNewValue());
                }
            }
        });
        fetchDataWorker.execute();
    }

    private void cancelClicked() {
        if (fetchDataWorker != null) {
            fetchDataWorker.cancel(true);
            JOptionPane.showMessageDialog(this, "Cancelled!");
        }
    }

    private class FetchDataWorker extends SwingWorker<List<Name>, Name> {

        // this method occurs in a thread
        protected List<Name> doInBackground() throws Exception {
            List<Name> allNames = new ArrayList<Name>();

            for (int i = 0; i < 20 && !isCancelled(); i++) {
                setProgress(i*5);
                TimeUnit.MILLISECONDS.sleep(750);

                Name[] nextBatchOfNames = new Name[5];
                for (int j = 0; j < 5; j++) {
                    nextBatchOfNames[j] = RandomName.generate();
                    allNames.add(nextBatchOfNames[j]);
                }

                // publish an intermediate batch of names
                super.publish(nextBatchOfNames);
            }
            setProgress(100);

            return allNames;
        }

        // this is called on the EDT
        protected void process(List<Name> chunks) {
            for (Name n : chunks) {
                nameListModel.addElement(n);
            }
        }

        // this is called on the EDT
        protected void done() {
            if (cancelButton.hasFocus()) {
                cancelButton.transferFocus();
            }
            cancelAction.setEnabled(false);
            startAction.setEnabled(true);
            progressBar.setVisible(false);
            fetchDataWorker = null;
        }
    }

}
